package cn.jinjimi.framework.helper.aspects;

import cn.jinjimi.framework.helper.annotation.EventTrace;
import cn.jinjimi.framework.helper.annotation.EventTraceParam;
import cn.jinjimi.framework.helper.annotation.EventTraceSelector;
import cn.jinjimi.framework.helper.annotation.EventTraceWhenFalse;
import cn.jinjimi.framework.helper.annotation.EventTraceWhenTrue;
import cn.jinjimi.app.AppContext;
import cn.jinjimi.framework.analy.AnalyManager;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by zhenhua on 16/12/28.
 */
@Aspect
public class EventTraceHelper {

    @Pointcut("execution(@com.jinjimi.framework.helper.annotation.EventTrace * *(..))")
    public void onEventTrace() {
    }

    @Around("onEventTrace()")
    public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        if (method != null && method.isAnnotationPresent(EventTrace.class)) {
            try {
                trace(method, joinPoint.getTarget(), joinPoint.getArgs());
            } catch (Exception e) {
                //e.printStackTrace();
            }
        }

        return joinPoint.proceed();
    }

    private void trace(Method method, Object target, Object[] args) throws Exception {

        EventTrace eventTrace = method.getAnnotation(EventTrace.class);
        String[] event_ids = eventTrace.value();

        String event = "";
        if (event_ids.length == 0)
            return;
        if (event_ids.length == 1) {
            event = event_ids[0];
        }

        boolean isDirectSend = eventTrace.isDirectSend();

        List<Parameter> parameters = new ArrayList<>();
        parameters.addAll(getParams(target));
        parameters.addAll(getParams(method, args));

        boolean doTrace = true;

        Map<String, String> params = new HashMap<>();

        for (Parameter p : parameters) {
            if (p != null && p.annotation != null) {
                Class<?> type = p.annotation.annotationType();
                if (type.equals(EventTraceWhenTrue.class)) {
                    if (p.value instanceof Boolean) {
                        doTrace = (Boolean) p.value;
                    }
                } else if (type.equals(EventTraceWhenFalse.class)) {
                    if (p.value instanceof Boolean) {
                        doTrace = !(Boolean) p.value;
                    }
                } else if (type.equals(EventTraceParam.class)) {
                    EventTraceParam param = ((EventTraceParam) p.annotation);
                    Object value = p.value;

                    String[] selector = param.selector();
                    if (selector.length >= 2) {
                        if (value instanceof Boolean) {
                            value = selector[((Boolean) value) ? 0 : 1];
                        } else if (value instanceof Integer && ((Integer) value < selector.length)) {
                            value = selector[(Integer) value];
                        }
                    }

                    boolean autoPlusOne = param.isValueAutoPlusOne();
                    if (autoPlusOne) {
                        if (value instanceof Integer) {
                            value = ((Integer) value) + 1;
                        }
                    }
                    params.put(((EventTraceParam) p.annotation).value(), String.valueOf(value));
                } else if (type.equals(EventTraceSelector.class)) {
                    if (p.value instanceof Integer) {
                        int sel = (Integer) p.value;
                        if (sel > -1 && sel < event_ids.length) {
                            event = event_ids[sel];
                        }
                    } else if (p.value instanceof Boolean) {
                        if (event_ids.length >= 2) {
                            event = event_ids[((Boolean) p.value) ? 0 : 1];
                        }
                    }
                }
            }
        }
        //添加EventTrace默认参数
        params.putAll(getParams(eventTrace));
        if (doTrace) {
            AnalyManager.getInstance().Event(AppContext.getInstance(), event, params, isDirectSend);
        }
    }


    private static class Parameter {
        Annotation annotation;
        Class<?> type;
        Object value;

        Parameter(Annotation annotation, Class<?> type, Object value) {
            this.annotation = annotation;
            this.type = type;
            this.value = value;
        }
    }

    private List<Parameter> getParams(Method method, Object[] args) {

        if (method == null || args == null || args.length == 0) {
            return Collections.emptyList();
        }

        List<Parameter> parameters = new ArrayList<>();
        Annotation[][] annotations = method.getParameterAnnotations();
        Class<?>[] types = method.getParameterTypes();

        for (int i = 0; i < annotations.length; i++) {
            Annotation[] group = annotations[i];
            for (Annotation a : group) {
                if (a.annotationType().equals(EventTraceParam.class)
                        || a.annotationType().equals(EventTraceWhenTrue.class)
                        || a.annotationType().equals(EventTraceWhenFalse.class)
                        || a.annotationType().equals(EventTraceSelector.class)
                        ) {
                    parameters.add(new Parameter(a, types[i], args[i]));
                }
            }
        }

        return parameters;
    }

    private List<Parameter> getParams(Object target) throws IllegalAccessException {
        if (target == null) {
            return Collections.emptyList();
        }

        List<Parameter> parameters = new ArrayList<>();
        Field[] fields = target.getClass().getDeclaredFields();

        for (Field f : fields) {
            Annotation[] annotations = f.getDeclaredAnnotations();
            for (Annotation a : annotations) {
                if (a.annotationType().equals(EventTraceParam.class)
                        || a.annotationType().equals(EventTraceWhenTrue.class)
                        || a.annotationType().equals(EventTraceWhenFalse.class)
                        || a.annotationType().equals(EventTraceSelector.class)) {
                    parameters.add(new Parameter(a, f.getType(), f.get(target)));
                }
            }
        }

        return parameters;
    }

    private Map<String, String> getParams(EventTrace eventTrace) {
        Map<String, String> parameters = new HashMap<>();
        if (eventTrace == null) {
            return parameters;
        }
        String[] dK = eventTrace.paramsK();
        String[] dV = eventTrace.paramsV();
        if (dK.length == 0 || dV.length == 0)
            return parameters;
        int lengthK = dK.length;
        int lengthV = dV.length;
        int cuttedLength = Math.min(lengthK, lengthV);
        for (int i = 0; i < cuttedLength; i++) {
            parameters.put(dK[i], dV[i]);
        }
        return parameters;
    }

}
